
#ifndef PUBSUB_HPP
#define PUBSUB_HPP
#include <memory>
#include <functional>
#include <vector>
#include <map>
#include <mutex>
#include <queue>
#include <set>
#include <algorithm>
#include <condition_variable>
#include <thread>

namespace pubsub {

    typedef std::vector<unsigned char> bytes;
    typedef std::function<void(const bytes &)> CallbackFunction;

    enum operation {
        sub,
        unsub,
        pub

    };

    class Query {
    public:
        virtual ~Query() = default;

        virtual bool matches(const std::map<std::string, std::string> &tags) = 0;

        virtual std::string toString() = 0;
    };

    class EmptyQuery:public Query
    {
    public:
        bool matches(const std::map<std::string, std::string> &tags) override
        {
            return true;
        }
        std::string toString(){
            return std::string("Empty query");
        }
    };

    struct cmd {
        std::string clientID;
        operation op;
        std::shared_ptr<Query> query;
        std::map<std::string, std::string> tags;
        std::vector<unsigned char> msg;
        CallbackFunction callbackFunction;
    };

    class State {
        friend class Server;

    public:
        void add(const std::string &clientID, std::shared_ptr<Query> query, const CallbackFunction &callbackFunction);

        void remove(const std::string &clientID, std::shared_ptr<Query> query);

        void removeAll(std::string clientID);

        void call(const bytes &msg, const std::map<std::string, std::string> &tags);

    private:
        // Query -> clientID -> CallbackFunction
        std::map<std::shared_ptr<Query>, std::map<std::string, CallbackFunction>> queries;

        // clientID -> Query
        std::map<std::string, std::set<std::shared_ptr<Query>>> clients;
    };

    class Server {
        friend class State;

    public:
        Server();

        ~Server();

        void subscribe(const std::string &clientID, std::shared_ptr<Query> query, const CallbackFunction &cbFunction);

        void unSubscribe(const std::string &clientID, std::shared_ptr<Query> query);

        void unSubscribeAll(const std::string &clientID);

        void publich(const bytes &msg);

        void publichWithTags(const bytes &msg, const std::map<std::string, std::string> &tags);

        void stop();

    private:
        std::thread worker_;
        std::mutex x_cmds_;
        std::queue<cmd> cmds_;   // We should deal with the cmd one by one
        std::condition_variable condition_;
        bool stop_;
        State state;

        std::mutex x_subscriptions_;
        std::map<std::string, std::map<std::string, std::shared_ptr<Query>>> subscriptions_; // clientID -> Query::toString() -> Query
    };

}
#endif